#include "Pch.h"

#include "PDBManager.h"
#include "RenderWindow.h"
#include "MainWindow.h"
#include <stdlib.h>
#include "glUtil.h"
#include "Config.h"
#include "Atom.h"

#include "GLFont.h"
#include "Visualizations.h"

RenderWindow *RenderWindow::lastDrawn = 0;
int RenderWindow::renderWindowCount = 0;
int RenderWindow::needContextInit = 1;

BEGIN_EVENT_TABLE(RenderWindow, ClickManager<wxGLCanvas>)
	//EVT_MOTION(BasicGLPane::mouseMoved)
	//EVT_LEFT_DOWN(BasicGLPane::mouseDown)
	//EVT_LEFT_UP(BasicGLPane::mouseReleased)
	//EVT_RIGHT_DOWN(BasicGLPane::rightClick)
	//EVT_LEAVE_WINDOW(BasicGLPane::mouseLeftWindow)
	EVT_ERASE_BACKGROUND(RenderWindow::DoNothing)
	EVT_SIZE(RenderWindow::OnSize)
	//EVT_KEY_DOWN(BasicGLPane::keyPressed)
	//EVT_KEY_UP(BasicGLPane::keyReleased)
	//EVT_MOUSEWHEEL(BasicGLPane::mouseWheelMoved)
	EVT_PAINT(RenderWindow::render)
END_EVENT_TABLE()


int wxGLCanvasInitArgs[] = {
	// 24 seems to work, too, but just being careful...
	// 32, at least, can break Linux.
	WX_GL_DEPTH_SIZE, 16,
	WX_GL_RGBA,
	WX_GL_DOUBLEBUFFER,
	0
};


void RenderWindow::DoNothing(wxEraseEvent& evt) {
}

// TODO:  Possible replacement for wxGLCanvasInitArgs under wxWidgets 0.2.9.
//        As it is, 32-bit depth buffer doesn't seem to work in my limited
//        Linux testing, and 0.2.8 doesn't have any official way to check
//        what works.
/*
int* GetGLCanvasInitArgs() {
	static int wxGLCanvasInitArgs[] = {
		WX_GL_DEPTH_SIZE, 32,
		WX_GL_RGBA,
		WX_GL_DOUBLEBUFFER,
		0
		// Also require wxWidgets 0.2.9 or later.
		// WX_GL_SAMPLE_BUFFERS, 1
		// WX_GL_SAMPLES, 4
	};
	for (int depth = 32; depth >= 16; depth-=8) {
		wxGLCanvasInitArgs[1] = depth;
		if (wxGLCanvas::IsDisplaySupported(wxGLCanvasInitArgs))
			return wxGLCanvasInitArgs;
	}
	return 0;
}
//*/

RenderWindow::RenderWindow(wxWindow *parent, MainWindow *mainWindow, PDBManager* pdbManager, int restrictChain, int labelChain) :
	ClickManager<wxGLCanvas>(parent, wxCLIP_SIBLINGS|wxBORDER_SUNKEN|wxFULL_REPAINT_ON_RESIZE)
{
	renderData = CreateRenderData(pdbManager);
	this->labelChain = labelChain;
	this->restrictChain = restrictChain;
	this->mainWindow = mainWindow;
	this->pdbManager = pdbManager;
	renderWindowCount++;
	GetClientSize(&width, &height);
	if (width <= 0) width = 1;
	if (height <= 0) height = 1;
	ResetCamera(0);
	dragging = 0;

	SetMinSize(wxSize(0,0));
}

RenderWindow::~RenderWindow() {
	// Just in case later make new RenderWindow in same memory location as the old one.
	lastDrawn = 0;

	if (renderData) {
		renderData->Release();
	}
	if (!--renderWindowCount) {
		context.Destroy(this);
	}
}


void RenderWindow::ConfigChangedUninit() {
	context.CleanupSharedData(this);
	if (renderData)
		renderData->Uninitialize();
	needContextInit = 1;
	lastDrawn = 0;
}

void RenderWindow::ConfigChangedInit() {
	context.Init(this);
	if (needContextInit) {
		InitContext();
	}
	if (renderData)
		renderData->Initialize();
}

void RenderWindow::InitContext() {
	SetCurrent(*context.wxglContext);

	glEnable(GL_LINE_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glLineWidth(1.2f);

	glClearColor(config->color3DBg.r, config->color3DBg.g, config->color3DBg.b, 1.0f);
	glShadeModel(GL_SMOOTH);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	glEnable(GL_CULL_FACE);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	// Mostly useful for debugging.
	//glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);

	// Get rid of extra lighting, so can specify everything together.
	float globalAmbient[3] = {0.0f, 0.0f, 0.0f};
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globalAmbient);

	float ambient[4] = {0.4f, 0.4f, 0.4f, 1.0f};
	float diffuse[4] = {0.6f, 0.6f, 0.6f, 1.0f};
	float specular[4] = {0.5f, 0.5f, 0.5f, 1.0f};

	float position[4] = {1.0f, 1.0f, 1.5f, 0.0f};

	float specColor[4] = {1.0f, 1.0f, 1.0f, 1.0f};

	glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
	glLightfv(GL_LIGHT0, GL_POSITION, position);

	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	glAlphaFunc(GL_NOTEQUAL, 0);

	glMateriali(GL_FRONT, GL_SHININESS, 100);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specColor);

	needContextInit = 0;
}

void RenderWindow::UpdateViewport(int selecting, double x, double y, int w, int h) {
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if (selecting) {
		GLint view[4] = {0, 0, width, height};
		// glGetIntegerv(GL_VIEWPORT, view);
		// Needs center of selection, rather than lower left corner.
		gluPickMatrix(x, height-1-y, w, h, view);
	}
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.5f,100.0f/pdbManager->scale);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0, 0, 2.414214/pdbManager->scale, 0, 0, 0, 0, 1, 0);
}

struct glHit {
	GLuint count; // Always 1
	GLuint min;
	GLuint max;
	GLuint name;
};

ResidueIndex *RenderWindow::GetObjects(int x1, int y1, int x2, int y2) {
	int residues = 0;
	for (int i=0; i<pdbManager->numChains; i++) {
		residues += pdbManager->chains[i]->numTotalResidues;
	}
	glHit *glHits = (glHit*) malloc(sizeof(glHit) * residues);
	glSelectBuffer(residues*4, &glHits->count);

	double x = (x1+x2)/2.0;
	double y = (y1+y2)/2.0;
	int w = abs(x1-x2)+1;
	int h = abs(y1-y2)+1;

	glRenderMode(GL_SELECT);
	glInitNames();
	glPushName(0);

	DrawScene(1, x, y, w, h);
	//pdbManager->statusBar->SetText(wxString::Format(_T("(%0.1lf %0.1lf), (%i, %i)"), x, y, w, h));

	int numHits = glRenderMode(GL_RENDER);

	int *indices = (int *) calloc(residues, sizeof(int));
	for (int i=0; i<numHits; i++) indices[glHits[i].name] = 1;
	free(glHits);

	ResidueIndex *hits = (ResidueIndex*) (malloc((numHits+1) * sizeof(ResidueIndex)));
	hits[numHits].model = hits[numHits].chain = hits[numHits].residue = -1;
	int base = 0;
	int count = 0;

	int c = 0;
	if (restrictChain >= 0) {
		// If only drawing one chain, residue 0 is the first residue of that chain.
		// Saves memory in mesh cache, as those are the indices used here, too.
		c = restrictChain;
	}
	for (; c<pdbManager->numChains; c++) {
		ManagedChain *chain = pdbManager->chains[c];
		for (int i=0; i<chain->numTotalResidues; i++) {
			if (indices[i+base]) {
				hits[count].model = pdbManager->GetChainModelIndex(c);
				hits[count].chain = c;
				hits[count].residue = i;
				count++;
			}
		}
		base += chain->numTotalResidues;
	}
	free(indices);
	return hits;
}


int RenderWindow::GetObject(int x, int y, int *out) {
	GLuint hits[10000];
	glSelectBuffer(sizeof(hits)/sizeof(GLuint), hits);

	glRenderMode(GL_SELECT);
	glInitNames();
	glPushName(0);

	DrawScene(1, x, y, 1, 1);

	int numHits = glRenderMode(GL_RENDER);
	if (!numHits) return 0;
	if (numHits) {
		int bestHit = 0;
		for (int i=1; i<numHits; i++) {
			if (hits[bestHit*4+1] > hits[i*4+1]) {
				bestHit = i;
			}
		}
		int index = hits[4*bestHit+3];

		int c = 0;
		if (restrictChain >= 0) {
			// If only drawing one chain, residue 0 is the first residue of that chain.
			// Saves memory in mesh cache, as those are the indices used here, too.
			c = restrictChain;
		}
		for (; c<pdbManager->numChains; c++) {
			ManagedChain *chain = pdbManager->chains[c];
			if (index >= chain->numTotalResidues) {
				index -= chain->numTotalResidues;
				continue;
			}
			out[0] = pdbManager->GetChainModelIndex(c);
			out[1] = c;
			out[2] = index;
			return 1;
		}
	}
	// ??
	return 0;
}

Transform *RenderWindow::GetCurrentTransform() {
	return &pdbManager->transform;
}

void RenderWindow::DrawScene(int selecting, double x, double y, int w, int h) {
	//if (context.wxglContext) context.Cleanup(this);
	if (!context.wxglContext) {
		context.Init(this);
		InitContext();
	}
	else if (lastDrawn != this) {
		// Can have performance impact, in some cases.
		SetCurrent(*context.wxglContext);
	}
	//SetCurrent(*context.wxglContext);
	lastDrawn = this;
	/*
	int t = timeGetTime();
	for (int i=0; i<100; i++) {
	//*/

	UpdateViewport(selecting, x, y, w, h);

	//glLoadIdentity();
	Transform *transform = GetCurrentTransform();

	glTranslatef((float)transform->translation.x, (float)transform->translation.y, (float)transform->translation.z);
	Matrix rotation;
	transform->CalcFullCurrentRotation(&rotation, pdbManager);
	mglMultMatrix(rotation);
	mglMultMatrix(transform->worldView);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	GLdouble modelView[4][4];
	GLdouble projection[4][4];
	double fullTransform[4][4];
	GLint viewport[4];
	glGetDoublev(GL_MODELVIEW_MATRIX, modelView[0]);
	glGetDoublev(GL_PROJECTION_MATRIX, projection[0]);
	glGetIntegerv(GL_VIEWPORT, viewport);

	for (int i=0; i<4; i++) {
		for (int j=0; j<4; j++) {
			fullTransform[i][j] = 0;
			for (int w=0; w<4; w++) {
				fullTransform[i][j] += projection[w][j] * modelView[i][w];
			}
		}
	}
	// Just in case using a new context, make sure color is set.
	mglColorInit();

	int numChains = pdbManager->numChains;
	ManagedChain **chains = pdbManager->chains;

	// Used both for selecting and keeping track of cached mesh data.
	int resNum = 0;

	mglMesh::Begin(config->drawMode, selecting);
	for (int c=0; c<numChains; c++) {
		ManagedChain *chain = chains[c];
		if (restrictChain >= 0 && restrictChain != c) {
			// Don't update resNum.
			continue;
		}

		for (int r = 0; r<chain->numTotalResidues; r++) {
			if (selecting) {
				glLoadName(resNum);
			}
			resNum++;
			unsigned int residueFlags = chain->residueInfo[r].flags;
			if (r >= chain->numChainResidues) {
				residueFlags &= ~RESIDUE_RENDER_CARTOON;
			}
			if (!(residueFlags & RESIDUE_RENDER_3D))
				continue;
			if (!strcmp(chain->residues[r].resName, "HOH"))
				continue;
			mglColor4(pdbManager->GetResidueColor(c, r));
			if (residueFlags & (RESIDUE_SELECTED | RESIDUE_LAST_SELECTED)) {
				residueFlags &= ~(RESIDUE_FORCED_COLOR | RESIDUE_COLOR_ATOMS);
			}
			Color residueColor = pdbManager->GetResidueColor(c, r);
			if (residueFlags & RESIDUE_RENDER_CARTOON) {
				renderData->DrawCartoon(c, r, residueColor);
			}

			if (residueFlags & RESIDUE_RENDER_BALL_AND_STICK) {
				renderData->DrawBallAndStick(c, r, residueFlags, residueColor);
			}

			if (residueFlags & RESIDUE_RENDER_SPACEFILL) {
				renderData->DrawSpaceFill(c, r, residueFlags, residueColor);
			}
		}
	}
	mglMesh::End();

	if (!selecting) {
		glDisable(GL_LIGHTING);

		glEnable(GL_BLEND);
		glEnable(GL_ALPHA_TEST);

		mglColor3(config->colorHBond);
		glBegin(GL_LINES);
		for (int m=0; m<pdbManager->numModels; m++) {
			for (h=0; h<pdbManager->models[m]->numHbonds; h++) {
				HBondInfo *hb = &pdbManager->models[m]->hBonds[h];
				if ((hb->chain1->residueInfo[hb->res1].flags | hb->chain2->residueInfo[hb->res2].flags) & RESIDUE_RENDER_HBONDS) {
					glVertex3dv(hb->chain1->atoms[hb->atom1].pos.v);
					glVertex3dv(hb->chain2->atoms[hb->atom2].pos.v);
				}
			}
		}
		glEnd();

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, width, 0, height, -1, 1);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		glClear(GL_DEPTH_BUFFER_BIT);

		GLFont *font = context.font;
		font->Begin();

		//font.Draw(_T("ABCDEliITT"), 250, 250);

		mglColor3(config->colorText3D);
		for (int c = 0; c<numChains; c++) {
			if (restrictChain >= 0 && restrictChain != c)
				continue;
			ManagedChain *chain = chains[c];
			for (int r = 0; r<chain->numTotalResidues; r++) {
				unsigned int residueFlags = chain->residueInfo[r].flags;
				if (!(residueFlags & RESIDUE_RENDER_LABEL)) continue;
				// mglColor(chain->GetResidueColor(r));
				int firstAtom = chain->residues[r].firstAtom;
				int lastAtom = chain->residues[r].lastAtom;
				if (firstAtom < 0) lastAtom = -100;
				for (int a = firstAtom; a <= lastAtom; a++) {
					PDBAtom *atom = &chains[c]->atoms[a];
					Vector *v = &atom->pos;

					if (!strcmp(atom->name, " CA ")) {
						double v2[4];
						for (int i=0; i<4; i++) {
							v2[i] = fullTransform[3][i];
							for (int j=0; j<3; j++) {
								v2[i] += fullTransform[j][i] * v->v[j];
							}
						}

						double flip = 1/v2[3];

						int x = floor(viewport[0] + viewport[2] * (v2[0] * flip + 1.0)*0.5 + 0.5);
						int y = floor(viewport[1] + viewport[3] * (v2[1] * flip + 1.0)*0.5 + 0.5);
						float z = (float) ((v2[2] * flip + 1.0)*-0.5);
						//char temp[100];
						//sprintf(temp, "%0.5lf", z);
						//font->Draw(wxString(temp, wxConvUTF8), x, y, z, DRAWFONT_HCENTER | DRAWFONT_VCENTER);
						font->Draw(chain->residues[r].resName, x, y, z, DRAWFONT_HCENTER | DRAWFONT_VCENTER);
					}
				}
				//break;
			}
		}

		glDisable(GL_DEPTH_TEST);
		if (labelChain && restrictChain >= 0) {
			font->Draw(pdbManager->chains[restrictChain]->idString, 2, this->height-2-font->height, 0, 0);
		}

		font->End();
		glDisable(GL_ALPHA_TEST);


		if (dragging) {
			mglColor4(128, 150, 255, 50);

			int x1, x2, y1, y2;
			GetDragRect(&x1, &y1, &x2, &y2);
			if (x1 > x2) {
				int temp = x1;
				x1 = x2;
				x2 = temp;
			}
			if (y1 > y2) {
				int temp = y1;
				y1 = y2;
				y2 = temp;
			}

			glBegin(GL_QUADS);
				glVertex2f(x1, height-y1);
				glVertex2f(x1, height-y2);
				glVertex2f(x2, height-y2);
				glVertex2f(x2, height-y1);
			glEnd();

			mglColor4(128, 150, 255, 255);
			glBegin(GL_LINE_STRIP);
				glVertex2f(x1, height-y1);
				glVertex2f(x1, height-y2);
				glVertex2f(x2, height-y2);
				glVertex2f(x2, height-y1);
				glVertex2f(x1, height-y1);
			glEnd();

		}
		glEnable(GL_DEPTH_TEST);

		glDisable(GL_BLEND);
		glEnable(GL_LIGHTING);
	}
	/*
	}
	int dt = timeGetTime() - t;
	if (selecting) {
		FILE *out = fopen("selecting.txt", "ab");
		fprintf(out, "%i\n", dt);
		fclose(out);
	}
	else {
		FILE *out = fopen("not selecting.txt", "ab");
		fprintf(out, "%i\n", dt);
		fclose(out);
	}//*/
}

void RenderWindow::render(wxPaintEvent& evt) {
	if(!IsShown()) return;

	wxPaintDC(this);

	DrawScene();

	//glFlush();
	SwapBuffers();
}

void RenderWindow::CenterCamera(int frame) {
	Vector center;
	Transform *transform = GetCurrentTransform();
	transformVect(&center, &transform->worldView, &pdbManager->selCenter);
	transform->translation.x = -center.x;
	transform->translation.y = -center.y;

	/*
	if (frame) {
		unsigned int selected = 0;
		{
			unsigned int min, max;
			pdbManager->GetMinMaxFlags(0, &min, &max);
			selected = max & RESIDUE_SELECTED;
		}
		double maxDist = 0;
		Matrix rot;
		CalcFullCurrentRotation(&rot);

		for (int m=0; m<pdbManager->numModels; m++) {
			LoadedModel *model = &pdbManager->models[m];
			Matrix mat, mat2;
			mulMat(&mat2, &worldView, &model->modelView);
			mulMat(&mat, &rot, &mat2);
			mat2 = identity;
			mat2.M[0][3] += translation.x;
			mat2.M[1][3] += translation.y;
			mat2.M[2][3] -= center.z;

			Matrix mat3;
			mulMat(&mat3, &mat2, &mat);
			mat = mat3;

			for (int c=0; c<pdbManager->numChains; c++) {
				LoadedChain *chain = &pdbManager->chains[c];
				if (chain->model != model->model) continue;
				for (int j=0; j<chain->chain->numAtoms; j++) {
					if (selected && !(chain->residueInfo[chain->chain->atoms[j].res].flags & selected)) continue;
					Vector v;
					transformVect(&v, &mat, &chain->chain->atoms[j].pos);
					double dist = lengthVect(&v);
					if (dist > maxDist) {
						maxDist = dist;
					}
				}
			}
		}

		translation.z = -maxDist;
		// 3 angstrom border.
		//maxDist += 3;

		//translation.z = (float)(1/maxDist*0.923879) - center.z;
	}
	//*/

	KeepCurrentRotation();
	ForceRedraw();
}

void RenderWindow::OnSize(wxSizeEvent& evt) {
	GetClientSize(&width, &height);
	windowScale = min(width*4/3.0, height)/2.0;
	if (windowScale <= 0) windowScale = 0.5;
}

void RenderWindow::DXDY(int startx, int starty, int lastx, int lasty) {
	Transform *transform = GetCurrentTransform();
	if (lastx == startx && lasty == starty) {
		transform->currentRot = identity;
	}
	else {
		double scale = windowScale/1.1;
		double sx = (startx - (width/2.0))/scale;
		double sy = -(starty - (height/2.0))/scale;
		double sx2 = (lastx - (width/2.0))/scale;
		double sy2 = -(lasty - (height/2.0))/scale;
		if (sx*sx + sy*sy >= 1 && sx2*sx2 + sy2*sy2 >= 1) {
			ArcBall(startx, starty, lastx, lasty, 0);
		}
		else {
			double dx = (lastx - startx)/scale;
			double dy = -(lasty - starty)/scale;
			Quaternion q;
			q.v.x = -dy;
			q.v.y = dx;
			q.v.z = 0;
			normalizeVect(&q.v);
			double angle = sqrt(dx*dx+dy*dy)*2;
			mulVect(&q.v, &q.v, sin(angle/2));
			q.w = -cos(angle/2);

			quatToMat(&transform->currentRot, &q);
		}
	}
	ResetStartPos();
	KeepCurrentRotation();
}

void RenderWindow::ArcBall(int startx, int starty, int lastx, int lasty, int smooth) {
	Transform *transform = GetCurrentTransform();
	if (lastx == startx && lasty == starty) {
		transform->currentRot = identity;
	}
	else {
		Vector start, end;
		double scale = windowScale/1.1;
		start.x = (startx - (width/2.0))/scale;
		start.y = -(starty - (height/2.0))/scale;
		start.z = 0;

		if (lengthVect(&start) > 1) normalizeVect(&start);

		if (smooth) {
			double len = lengthVect(&start);
			len = sin(len * 3.14159/2);
			normalizeVect(&start);
			mulVect(&start, &start, len);
		}

		double t = 1 - start.x * start.x - start.y * start.y;
		if (t > 0) {
			start.z = sqrt(t);
		}

		end.x = (lastx - (width/2.0))/scale;
		end.y = -(lasty - (height/2.0))/scale;
		end.z = 0;
		if (lengthVect(&end) > 1) normalizeVect(&end);

		if (smooth) {
			double len = lengthVect(&end);
			len = sin(len * 3.14159/2);
			normalizeVect(&end);
			mulVect(&end, &end, len);
		}

		t = 1 - end.x * end.x - end.y * end.y;
		if (t > 0) {
			end.z = sqrt(t);
		}

		normalizeVect(&start);
		normalizeVect(&end);

		/*Matrix temp = renderWindow.modelView;
		temp.M[0][3] = 0;
		temp.M[1][3] = 0;
		temp.M[2][3] = 0;
		Vector start2, end2;
		transformVect(&start2, &temp, &start);
		start = start2;
		transformVect(&end2, &temp, &end);
		end = end2;//*/

		Quaternion q;
		//*
		crossVect(&q.v, &start, &end);
		normalizeVect(&q.v);
		double angle = 2*acos(dotVect(&start, &end));
		//if (smooth) angle /= 2;
		mulVect(&q.v, &q.v, sin(angle/2));
		q.w = -cos(angle/2);

		quatToMat(&transform->currentRot, &q);
	}
}

void RenderWindow::MouseWheel(unsigned int wheel, int delta, unsigned int flags, int lastActionWasWheel) {
	Transform *transform = GetCurrentTransform();
	if (wheel == 0) {
		transform->translation.z += delta/5.0 / pdbManager->scale;
		if (!lastActionWasWheel && (!(flags & MOUSE_BUTTONS) || dragging)) {
			lastActionWasWheel = 1;
		}
		if (dragging) {
			int startx, starty, lastx, lasty;
			GetDragRect(&startx, &starty, &lastx, &lasty);
			UpdateDragSel(startx, starty, lastx, lasty);
		}
		else {
			ForceRedraw();
		}
	}
}

void RenderWindow::MouseMove(unsigned int flags, int x, int y) {
	int model, chain, residue;
	if (!(flags & MOUSE_LEAVING) && GetClickedResidue(x, y, &model, &chain, &residue)) {
		mainWindow->statusBar->SetResidue(chain, residue);
	}
	else {
		mainWindow->statusBar->SetResidue(-1, -1);
	}
}

void RenderWindow::Translate(int startx, int starty, int lastx, int lasty) {
	Transform *transform = GetCurrentTransform();
	transform->translation.x += (lastx - startx)/(2*windowScale)/pdbManager->scale;
	transform->translation.y -= (lasty - starty)/(2*windowScale)/pdbManager->scale;
}

void RenderWindow::Zoom(int starty, int lasty) {
	Transform *transform = GetCurrentTransform();
	transform->translation.z += -(lasty - starty)/(0.2*windowScale)/pdbManager->scale;
}

void RenderWindow::MouseDown(unsigned int button, int clickCount, unsigned int flags, int x, int y) {
	DragDone(1);
	KeepCurrentRotation();

	if (button == MOUSE_MIDDLE) {
		CenterCamera(0);
		ForceRedraw();
	}

	if ((flags & MOUSE_BUTTONS) == MOUSE_LEFT) {
		if (clickCount == 1) {
			pdbManager->SaveUndoState();
		}
		if (flags & MOUSE_DRAGGING) {
			if ((flags & MOUSE_SHIFT)) {
				dragging = 1;
				UpdateDragSel(x, y, x, y);
			}
		}
		else if (clickCount > 1) {
			MultipleClick(clickCount);
		}
	}
}

void RenderWindow::MouseDrag(unsigned int flags, int xStart, int yStart, int xStop, int yStop) {
	mainWindow->statusBar->SetResidue(-1, -1);
	flags &= ~MOUSE_MIDDLE;
	unsigned int buttons = (flags & MOUSE_BUTTONS) & ~MOUSE_MIDDLE;

	// Just in case don't receive shift up message promptly.
	if (dragging && (!(flags & MOUSE_SHIFT) || !(flags & MOUSE_LEFT))) {
		ResetStartPos();
		dragging = 0;
	}

	if (buttons == MOUSE_LEFT) {
		if (flags & MOUSE_SHIFT) {
			if (!dragging) {
				ResetStartPos();
				xStart = xStop;
				yStart = yStop;
				KeepCurrentRotation();
			}
			dragging = 1;
			UpdateDragSel(xStart, yStart, xStop, yStop);
		}
		else {
			if (config->rotationMode == 0) {
				ArcBall(xStart, yStart, xStop, yStop, 0);
			}
			else if (config->rotationMode == 1) {
				ArcBall(xStart, yStart, xStop, yStop, 1);
			}
			else {
				DXDY(xStart, yStart, xStop, yStop);
			}
			ForceRedraw();
		}
	}
	else if (buttons == MOUSE_RIGHT) {
		Translate(xStart, yStart, xStop, yStop);
		ResetStartPos();
		ForceRedraw();
	}
	else if (buttons == (MOUSE_RIGHT | MOUSE_LEFT)) {
		Zoom(yStart, yStop);
		ResetStartPos();
		ForceRedraw();
	}
}

void RenderWindow::MouseUp(unsigned int button, int clickCount, unsigned int flags, int x, int y) {
	if (dragging) {
		if (button == MOUSE_LEFT) {
			DragDone(0);
			ResetStartPos();
		}
		else if (button == MOUSE_SHIFT) {
			DragDone(0);
			ResetStartPos();
		}
		return;
	}
	if (button & MOUSE_FXN_KEYS)
		return;
	KeepCurrentRotation();
	// If dragged or more than one down, click count is 0.
	if (clickCount >= 1) {
		if (button == MOUSE_LEFT) {
			// Multiple left clicks handled on press.
			if (clickCount == 1) {
				int startx, starty, lastx, lasty;
				GetDragRect(&startx, &starty, &lastx, &lasty);
				SelectClickedResidue(startx, starty);
			}
		}
		else if (button == MOUSE_RIGHT) {
			mainWindow->ContextMenu();
			ResetClickCount();
		}
	}
	ResetStartPos();
}

void RenderWindow::CaptureLost() {
	KeepCurrentRotation();
	DragDone(1);
}

void RenderWindow::KeepCurrentRotation() {
	GetCurrentTransform()->KeepCurrentRotation(pdbManager);
}

void RenderWindow::Refresh(bool eraseBackground, const wxRect *rect) {
	wxGLCanvas::Refresh(0, rect);
}

void RenderWindow::ResetZoom(int redraw) {
	// Just in case
	ResetMouseState();
	Transform *transform = GetCurrentTransform();
	transform->translation.x = transform->translation.y = transform->translation.z = 0;
	if (redraw) ForceRedraw();
}

void RenderWindow::ResetOrientation(int redraw) {
	// Just in case
	ResetMouseState();
	Transform *transform = GetCurrentTransform();
	transform->worldView = transform->currentRot = identity;
	if (redraw) ForceRedraw();
}

void RenderWindow::ResetCamera(int redraw) {
	ResetZoom(0);
	ResetOrientation(redraw);
}

void RenderWindow::ForceRedraw(int all) {
	if (!all)
		pdbManager->Redraw(REDRAW_RENDER | REDRAW_NOW);
	else
		pdbManager->Redraw(REDRAW_ALL | REDRAW_NOW);
	//Refresh();

	// If double buffering, safer just redrawing everything,
	// though slower.
	// pdbManager->Refresh();
}

int RenderWindow::GetClickedResidue(int x, int y, int *model, int *chain, int *residue) {
	int info[3];
	int hit = GetObject(x, y, info);
	if (hit) {
		*model = info[0];
		*chain = info[1];
		*residue = info[2];
		return 1;
	}
	else {
		*model = *chain = *residue = -1;
		return 0;
	}
}

void RenderWindow::SelectClickedResidue(int x, int y) {
	if (GetClickedResidue(x, y, &lastModel, &lastChain, &lastResidue)) {
		ManagedChain *chain = pdbManager->chains[lastChain];
		chain->residueInfo[lastResidue].flags ^= RESIDUE_SELECTED;

		pdbManager->UpdateResidues(0, 0, RESIDUE_LAST_SELECTED, 0);

		// pdbManager->ChangeSelection(lastChain, lastResidue);
		if (chain->residueInfo[lastResidue].flags & RESIDUE_SELECTED) {
			chain->residueInfo[lastResidue].flags |= RESIDUE_LAST_SELECTED;
			//pdbManager->UpdateChecks();
			//pdbManager->statusBar->SetResidueText(lastChain, lastResidue);
			//pdbManager->ChangeSelection(lastChain, lastResidue);
			//mainWindow.UpdateStatus(chain, index);
		}
		else {
			//pdbManager->statusBar->SetResidueText(-1, -1);
			//mainWindow.UpdateStatus(0, 0);
		}//*/
		pdbManager->Redraw(REDRAW_ALL, lastChain, lastResidue);
	}
	else {
		pdbManager->UpdateSelectedResidues(0, RESIDUE_SELECTED | RESIDUE_LAST_SELECTED, REDRAW_ALL);
		//pdbManager->statusBar->SetResidueText(-1, -1);
		ResetClickCount();
		//mainWindow.UpdateStatus(0, 0);
	}
}

void RenderWindow::UpdateDragSel(int startx, int starty, int lastx, int lasty) {
	wxMouseState mouseState = wxGetMouseState();
	if (mouseState.LeftDown() && mouseState.ShiftDown()) {
		// Make rendering a little more responsive while dragging.
		// Experimentally, difference isn't terribly noticeable.
		lastx = mouseState.GetX();
		lasty = mouseState.GetY();
		ScreenToClient(&lastx, &lasty);
	}
	pdbManager->UpdateResidues(0, 0, RESIDUE_LAST_SELECTED, 0);
	ResidueIndex *indices = GetObjects(startx, starty, lastx, lasty);
	if (indices) {
		ResidueIndex *res = indices;
		while (res->model >= 0) {
			pdbManager->chains[res->chain]->residueInfo[res->residue].flags |= RESIDUE_LAST_SELECTED;
			res++;
		}
		free(indices);
	}
	mainWindow->statusBar->UpdateSelectionCount();

	// Force a redraw before the next move event.  As move events take a while,
	// won't redraw until mouse stops, otherwise.
	ForceRedraw(1);
}

void RenderWindow::DragDone(int cancelled) {
	if (dragging) {
		dragging = 0;
		if (cancelled) {
			pdbManager->UpdateSelectedResidues(0, RESIDUE_LAST_SELECTED, REDRAW_ALL);
		}
		else {
			pdbManager->UpdateResidues(RESIDUE_LAST_SELECTED, RESIDUE_SELECTED, RESIDUE_LAST_SELECTED, REDRAW_ALL);
		}
	}
}

void RenderWindow::MultipleClick(int clickCount) {
	if (clickCount < 2) return;
	if (lastChain < 0) {
		ResetClickCount();
		return;
	}
	ManagedChain *chain = pdbManager->chains[lastChain];
	int numResidues = chain->numTotalResidues;
	int secondary = chain->residueInfo[lastResidue].flags & RESIDUE_SECONDARY;
	if (clickCount == 2) {
		for (int delta = -1; delta < 2; delta+=2) {
			// Start at last residue to make sure it's selected.
			for (int res = lastResidue; res>=0 && res < numResidues; res += delta) {
				if ((chain->residueInfo[res].flags & RESIDUE_SECONDARY) != secondary)
					break;
				chain->residueInfo[res].flags |= RESIDUE_SELECTED;
			}
		}
	}
	else if (clickCount == 3 && (secondary & RESIDUE_IS_BETA)) {
		// Use RESIDUE_LAST_SELECTED as temporary flag.  Second should already be set.
		chain->residueInfo[lastResidue].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
		// Only set it back to 1 when I find a new beta strand, as all the old ones will be fully selected.
		int changed = 1;
		while (changed) {
			changed = 0;
			for (int res=0; res<numResidues-1; res++) {
				if (chain->residueInfo[res].flags & RESIDUE_LAST_SELECTED && (chain->residueInfo[res+1].flags & RESIDUE_IS_BETA))
					chain->residueInfo[res+1].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
			}
			for (int res=numResidues-1; res > 0; res--) {
				if (chain->residueInfo[res].flags & RESIDUE_LAST_SELECTED && (chain->residueInfo[res-1].flags & RESIDUE_IS_BETA))
					chain->residueInfo[res-1].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
			}

			for (int i=0; i<chain->numBetaPairs; i++) {
				int s1 = chain->betaPairs[i].start1;
				int s2 = chain->betaPairs[i].start2;
				// Lazy check to see if one's flagged and the other isn't.
				if ((chain->residueInfo[s1].flags ^ chain->residueInfo[s2].flags) & RESIDUE_LAST_SELECTED) {
					chain->residueInfo[s1].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
					chain->residueInfo[s2].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
					changed = 1;
				}
			}
		}
	}
	else {
		pdbManager->SelectChain(lastChain);
		ResetClickCount();
	}
	// pdbManager->statusBar->SetResidueText(-1, -1);
	// Clear last selected residue, if set.  Also redraw and update flags.
	pdbManager->UpdateSelectedResidues(0, RESIDUE_LAST_SELECTED, REDRAW_ALL);
}

void RenderWindow::UpdatePDB(PDBManager *manager, int restrictChain, int labelChain, RenderData **renderData) {
	this->labelChain = labelChain;
	pdbManager = manager;
	this->restrictChain = restrictChain;

	this->renderData->Release();
	if (*renderData) {
		this->renderData = renderData[0];
		this->renderData->AddRef();
	}
	else {
		this->renderData = renderData[0] = CreateRenderData(manager);
	}
	ResetCamera(0);
}
